package com.klikmee.sms.core.base.communicator.starion;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.Date;
import java.util.List;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.util.URIUtil;
import org.apache.log4j.Logger;

import com.klikmee.sms.core.base.Core;
import com.klikmee.sms.core.base.communicator.DeliveryReport;
import com.klikmee.sms.core.base.communicator.SendSmsIntercepter;
import com.klikmee.sms.core.base.dao.ProviderDAO;
import com.klikmee.sms.core.base.handlers.ExceptionHandler;
import com.klikmee.sms.core.base.util.Constants;
import com.klikmee.sms.core.base.util.DeliveryReports;
import com.klikmee.sms.core.model.BulkMessage;
import com.klikmee.sms.core.model.IncomingMessage;
import com.klikmee.sms.core.model.OutgoingMessage;
import com.klikmee.sms.core.model.Provider;

/**
 * Implementation of the starion communicator, responsible for sending and
 * receiving messages over starion provider. *
 * 
 * @author makcro
 * 
 */
public class StarionCommunicatorImpl implements StarionCommunicator {

	/**
	 * Logger instance
	 */
	private Logger logger = Logger.getLogger(StarionCommunicatorImpl.class);

	/**
	 * Instance of core context
	 */
	private Core core = null;

	/**
	 * URL of Starion's server used to send sms messages
	 */
	private String smsCenterUrl = null;

	/**
	 * List of send SMS intercepters for this communicator
	 */
	private List<SendSmsIntercepter> smsIntercepters;

	/**
	 * Instance of starton provider
	 */
	private Provider startionProvider;

	private ProviderDAO providerDAO = null;

	/**
	 * Instance of default exception handler
	 */
	private ExceptionHandler exceptionHandler = null;

	public void init() {
		this.startionProvider = providerDAO.findByName(Constants.PROVIDER_STARION);
		logger.info( "Staroin communicator initialized." );
	}

	/**
	 * Sends message via Starion provider. Utilizes http API for sending
	 * messages. After successful sending,
	 * {@link Core#onMessageSent(OutgoingMessage)} call back is invoked.
	 * 
	 * @param outgoingMessage
	 *            outgoing message to be sent
	 */
	@Override
	public void sendMessage(OutgoingMessage outgoingMessage) {
		HttpClient httpClient = new HttpClient();
		HttpMethod httpMethod = new GetMethod(smsCenterUrl);

		try {

			NameValuePair[] queryStringParams = assembleQueryStringParams(outgoingMessage);
			httpMethod.setQueryString(queryStringParams);

			int result = httpClient.executeMethod(httpMethod);

			if (result != HttpStatus.SC_OK) {
				logger.fatal(MessageFormat.format("Invalid http status while sending message id = [{}]", outgoingMessage.getId()));
			}

			String response = httpMethod.getResponseBodyAsString();

			if (response.equals(SendSmsResponse.OK.getValue())) {
				core.onMessageSent(outgoingMessage);
				logger.info(MessageFormat.format("Successfully sent message with id [{0}]", outgoingMessage.getId()));
			}
			else {
				logger.fatal( MessageFormat.format( "Failed to send message with starion response [{0}]", response ));
			}

		} catch (HttpException e) {
			this.exceptionHandler.handle(e);
		} catch (IOException e) {
			this.exceptionHandler.handle(e);
		} finally {
			httpMethod.releaseConnection();
		}
	}

	/**
	 * Creates parameters for send SMS query string by mapping
	 * {@link OutgoingMessage} properties to name-value pairs.
	 * 
	 * @param outgoingMessage
	 *            outgoing message that is being sent
	 * @return an array of {@link NameValuePair} representing message parameter
	 *         and it's values.
	 * @throws UnsupportedEncodingException 
	 * @throws URIException
	 */
	private NameValuePair[] assembleQueryStringParams(OutgoingMessage outgoingMessage) throws UnsupportedEncodingException  {
		NameValuePair[] result = new NameValuePair[4];
		result[0] = new NameValuePair(SendSmsParams.MSG_ID.getValue(), outgoingMessage.getProvidersMesssageId());
//		result[1] = new NameValuePair(SendSmsParams.MESSAGE.getValue(), URLEncoder.encode(outgoingMessage.getMessage(), "UTF-8"));
		result[1] = new NameValuePair(SendSmsParams.MESSAGE.getValue(), outgoingMessage.getMessage());

		result[2] = new NameValuePair(SendSmsParams.CHARGE.getValue(), outgoingMessage.getPaymentInvoiceList().iterator().next()
				.getServiceChargingCategory().getTelenorChargingCode());

		result[3] = new NameValuePair(SendSmsParams.TYPE.getValue(), MessageType.TEXT.getValue());
		return result;
	}

	@Override
	public void sendBulkMessage(BulkMessage bulkMessage) {
		throw new UnsupportedOperationException();
	}

	@Override
	public List<SendSmsIntercepter> getSendSmsIntercepters() {
		return smsIntercepters;
	}

	/**
	 * Called when new sms message request is received over
	 * {@link SmsRequestHandlerImpl} instance. Constructs
	 * {@link IncomingMessage} and enqueues it for processing by calling
	 * {@link Core#enqueueIncomingMessage(IncomingMessage)}
	 */
	@Override
	public void onSmsMessage(String senderId, String shortId, String providerId, String messageText, String starionMessageId, String sdp, String operator) {

		// create incoming message instance
		IncomingMessage message = new IncomingMessage();
		message.setFromNumber(senderId);
		message.setMessage(messageText);
		message.setToNumber(shortId);
		message.setProviderMessageId(starionMessageId);
		message.setProvider(startionProvider);
		message.setOurIncomingTime(new Date());
		
		core.enqueueIncomingMessage(message);

		logger.info(MessageFormat.format("Forwarded new message request to core with provider's msg id = [{0}]", starionMessageId));

	}

	@Override
	public void onDeliveryReport(String startionMessageId, String charged) {

		DeliveryReport message = new DeliveryReport();

		// resolve delivery report value
		int deliveryReport = resolveDeliveryReport(charged);

		message.setProvidersMessageId(startionMessageId);
		message.setStatus(deliveryReport);
		message.setProvider(startionProvider);

		core.enqueueStatusReport(message);
	}

	/**
	 * Resolves starion's delivery report value against core context
	 * 
	 * @param value
	 *            starion's delivery report value
	 * @return integer representing one of the {@link DeliveryReports.Starion}
	 *         constants.
	 */
	private int resolveDeliveryReport(String value) {

		if (value.equals(StarionDeliveryReportValues.CHARGED.getValue())) {
			return DeliveryReports.DELIVERED;
		}

		else {
			return DeliveryReports.Starion.NOT_CHARGED;
		}

	}
	
	@Override
	public boolean canHandleBulkMessage(String receiver) {
		// TODO Auto-generated method stub
		return false;
	}

	public void setSmsIntercepters(List<SendSmsIntercepter> smsIntercepters) {
		this.smsIntercepters = smsIntercepters;
	}

	public void setProviderDAO(ProviderDAO providerDAO) {
		this.providerDAO = providerDAO;
	}

	public void setCore(Core core) {
		this.core = core;
	}

	public void setExceptionHandler(ExceptionHandler exceptionHandler) {
		this.exceptionHandler = exceptionHandler;
	}

	public void setSmsCenterUrl(String smsCenterUrl) {
		this.smsCenterUrl = smsCenterUrl;
	}

	public enum StarionDeliveryReportValues {

		CHARGED("OK"), NOT_CHARGED("NO");

		private String value;

		private StarionDeliveryReportValues(String value) {
			this.value = value;
		}

		public String getValue() {
			return value;
		}
	}

	/**
	 * Possible responses to the send sms request
	 */
	public enum SendSmsResponse {
		OK("OK"), NO("NO");

		private String value;

		private SendSmsResponse(String value) {
			this.value = value;
		}

		public String getValue() {
			return value;
		}
	}

	/**
	 * Enumeration of Starion's send sms parameters which could be put into
	 * URL's query string in http get request.
	 * 
	 */
	public enum SendSmsParams {
		MSG_ID("msg_id"), CHARGE("charge"), TYPE("type"), MESSAGE("message");

		private SendSmsParams(String value) {
			this.value = value;
		}

		private String value;

		public String getValue() {
			return value;
		}
	}

	/**
	 * Enumeration of starion's charging options, denoting should a message be
	 * charged or not.
	 * 
	 */
	public enum ChargingOptions {
		YES("OK"), NO("NO");

		private String value;

		private ChargingOptions(String value) {
			this.value = value;
		}

		public String getValue() {
			return value;
		}
	}

	/**
	 * Enumeration of starion's message types;
	 * 
	 */
	public enum MessageType {

		TEXT("TEXT,"), // sms text message
		WAP("WAP"); // wap push message

		private String value;

		private MessageType(String value) {
			this.value = value;
		}

		public String getValue() {
			return value;
		}
	}

	@Override
	public Provider getProvider() {
		// TODO Auto-generated method stub
		return null;
	}

	
}
